﻿//StrategyHelper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Strategies
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Common;
	using Ecng.Serialization;

	using StockSharp.Algo.Candles;
	using StockSharp.BusinessEntities;
	using StockSharp.Logging;

	/// <summary>
	/// Вспомогательный класс для работы с классом <see cref="Strategy"/>.
	/// </summary>
	public static class StrategyHelper
	{
		/// <summary>
		/// Создать инициализированный объект заявки на покупку по рыночной цене.
		/// </summary>
		/// <remarks>
		/// Заявка не регистрируется, а только создается объект.
		/// </remarks>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="volume">Объем. Если передается значение 0, то используется значение <see cref="Strategy.Volume"/>.</param>
		/// <returns>Инициализированный объект заявки.</returns>
		public static Order BuyAtMarket(this Strategy strategy, decimal volume = 0)
		{
			var order = strategy.CreateOrder(OrderDirections.Buy, 0, volume);

			var security = strategy.Security;

			if (security == null)
				throw new InvalidOperationException("Для стратегии {0} не задан инструмент.".Put(strategy));

			if (security.ExchangeBoard.IsSupportMarketOrders)
				order.Type = OrderTypes.Market;
			else
				order.Price = security.GetMarketPrice(OrderDirections.Buy);

			return order;
		}

		/// <summary>
		/// Создать инициализированный объект заявки на продажу по рыночной цене.
		/// </summary>
		/// <remarks>
		/// Заявка не регистрируется, а только создается объект.
		/// </remarks>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="volume">Объем. Если передается значение 0, то используется значение <see cref="Strategy.Volume"/>.</param>
		/// <returns>Инициализированный объект заявки.</returns>
		public static Order SellAtMarket(this Strategy strategy, decimal volume = 0)
		{
			var order = strategy.CreateOrder(OrderDirections.Sell, 0, volume);

			if (strategy.Security.ExchangeBoard.IsSupportMarketOrders)
				order.Type = OrderTypes.Market;
			else
				order.Price = strategy.Security.GetMarketPrice(OrderDirections.Sell);

			return order;
		}

		/// <summary>
		/// Создать инициализированный объект заявки на покупку.
		/// </summary>
		/// <remarks>
		/// Заявка не регистрируется, а только создается объект.
		/// </remarks>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="price">Цена.</param>
		/// <param name="volume">Объем. Если передается значение 0, то используется значение <see cref="Strategy.Volume"/>.</param>
		/// <returns>Инициализированный объект заявки.</returns>
		public static Order BuyAtLimit(this Strategy strategy, decimal price, decimal volume = 0)
		{
			return strategy.CreateOrder(OrderDirections.Buy, price, volume);
		}

		/// <summary>
		/// Создать инициализированный объект заявки на продажу.
		/// </summary>
		/// <remarks>
		/// Заявка не регистрируется, а только создается объект.
		/// </remarks>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="price">Цена.</param>
		/// <param name="volume">Объем. Если передается значение 0, то используется значение <see cref="Strategy.Volume"/>.</param>
		/// <returns>Инициализированный объект заявки.</returns>
		public static Order SellAtLimit(this Strategy strategy, decimal price, decimal volume = 0)
		{
			return strategy.CreateOrder(OrderDirections.Sell, price, volume);
		}

		/// <summary>
		/// Создать инициализированный объект заявки.
		/// </summary>
		/// <remarks>
		/// Заявка не регистрируется, а только создается объект.
		/// </remarks>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="direction">Направление заявки.</param>
		/// <param name="price">Цена.</param>
		/// <param name="volume">Объем. Если передается значение 0, то используется значение <see cref="Strategy.Volume"/>.</param>
		/// <returns>Инициализированный объект заявки.</returns>
		public static Order CreateOrder(this Strategy strategy, OrderDirections direction, decimal price, decimal volume = 0)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			return new Order
			{
				Portfolio = strategy.Portfolio,
				Security = strategy.Security,
				Direction = direction,
				Price = price,
				Volume = volume == 0 ? strategy.Volume : volume,
				Comment = strategy.CommentOrders ? strategy.Name : string.Empty,
			};
		}

		/// <summary>
		/// Закрыть открытую позицию по рынку (выставить заявку типа <see cref="OrderTypes.Market"/>).
		/// </summary>
		/// <remarks>
		/// Рыночная заявка не работает на всех биржах.
		/// </remarks>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="slippage">Уровень проскальзывания, допустимый при регистрации заявки. Используется, если заявка регистрируется лимиткой.</param>
		public static void ClosePosition(this Strategy strategy, decimal slippage = 0)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			var position = strategy.Position;

			if (position != 0)
			{
				var volume = position.Abs();

				var order = position > 0 ? strategy.SellAtMarket(volume) : strategy.BuyAtMarket(volume);

				if (order.Type != OrderTypes.Market)
				{
					order.Price += (order.Direction == OrderDirections.Buy ? slippage : -slippage);
				}

				strategy.RegisterOrder(order);
			}
		}

		/// <summary>
		/// Открыть позицию через котирование.
		/// </summary>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="finishPosition">Значение позиции, которое необходимо достичь. Отрицательное значение означает короткую позицию.</param>
		public static void OpenPositionByQuoting(this Strategy strategy, decimal finishPosition)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			var position = strategy.Position;

			if (finishPosition == position)
				return;

			var delta = (finishPosition - position).Abs();

			var quoting = new MarketQuotingStrategy(finishPosition < position ? OrderDirections.Sell : OrderDirections.Buy, delta);
			strategy.ChildStrategies.Add(quoting);
		}

		/// <summary>
		/// Закрыть открытую позицию через котирование.
		/// </summary>
		/// <param name="strategy">Стратегия.</param>
		public static void ClosePositionByQuoting(this Strategy strategy)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			var position = strategy.Position;

			if (position == 0)
				return;

			var quoting = new MarketQuotingStrategy(position > 0 ? OrderDirections.Sell : OrderDirections.Buy, position.Abs());
			strategy.ChildStrategies.Add(quoting);
		}

		/// <summary>
		/// Получить биржевое время.
		/// </summary>
		/// <param name="strategy">Стратегия.</param>
		/// <returns>Биржевое время.</returns>
		public static DateTime GetMarketTime(this Strategy strategy)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			return strategy.Security == null ? LoggingHelper.Now : strategy.Security.GetMarketTime();
		}

		/// <summary>
		/// Получить менеджер свечек, ассоциированный с переданной стратегией.
		/// </summary>
		/// <param name="strategy">Стратегия.</param>
		/// <returns>Менеджер свечек.</returns>
		public static ICandleManager GetCandleManager(this Strategy strategy)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			return strategy.Environment.GetValue<ICandleManager>("CandleManager");
		}

		/// <summary>
		/// Восстановить состояние стратегии.
		/// </summary>
		/// <remarks>
		/// Данный метод используется для загрузки статистики, заявок и сделок.
		/// 
		/// Хранилище данных должно содержать следующие параметры:
		/// 1. Settings (SettingsStorage) - настройки стратегии.
		/// 2. Statistics(SettingsStorage) - сохраненное состояние статистики.
		/// 3. Orders (IDictionary[Order, IEnumerable[MyTrade]]) - заявки и сделки по ним.
		/// 4. Positions (IEnumerable[Position]) - позиции стратегии.
		/// 
		/// При отсутствии одного из параметров соответствующие данные восстанавливаться не будут.
		/// </remarks>
		/// <param name="strategy">Стратегия.</param>
		/// <param name="storage">Хранилище данных.</param>
		public static void LoadState(this Strategy strategy, SettingsStorage storage)
		{
			if (strategy == null)
				throw new ArgumentNullException("strategy");

			if (storage == null)
				throw new ArgumentNullException("storage");

			var settings = storage.GetValue<SettingsStorage>("Settings");
			if (settings != null && settings.Count != 0)
			{
				if (settings.Contains("security"))
					strategy.Security = strategy.Trader.Securities.FirstOrDefault(s => s.Id == settings.GetValue<string>("security"));

				if (settings.Contains("portfolio"))
					strategy.Portfolio = strategy.Trader.Portfolios.FirstOrDefault(p => p.Name == settings.GetValue<string>("portfolio"));

				var id = strategy.Id;

				strategy.Load(settings);

				if (strategy.Id != id)
					throw new InvalidOperationException();
			}

			var statistics = storage.GetValue<SettingsStorage>("Statistics");
			if (statistics != null)
			{
				foreach (var parameter in strategy.StatisticManager.Parameters.Where(parameter => statistics.ContainsKey(parameter.Name)))
				{
					parameter.Load(statistics.GetValue<SettingsStorage>(parameter.Name));
				}
			}

			var orders = storage.GetValue<IDictionary<Order, IEnumerable<MyTrade>>>("Orders");
			if (orders != null)
			{
				foreach (var pair in orders)
				{
					strategy.AttachOrder(pair.Key, pair.Value);
				}
			}

			var positions = storage.GetValue<IEnumerable<Position>>("Positions");
			if (positions != null)
			{
				strategy.PositionManager.Positions = positions;
			}
		}
	}
}